#!/usr/bin/env python


import os
import sys
import cmd
import socket
from optparse import OptionParser

from dexen.system.common import service
from dexen.system.common.meta import ScoreMeta
from dexen.system.common.states import JobState
from dexen.system.client.proxy import ServerProxy
from dexen.system.common.excepts import AllNodeBusyException

#===============================================================================
# Constants
#===============================================================================
MY_EXIT = -999
ARG_ERR_MSG = "Incorrect number of arguments"

#===============================================================================
# MyCmd
#===============================================================================
class MyCmd(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)
        self.server = ServerProxy()
        self.prompt = "DEXEN>"
        self.jobs = {}
            
    def do_connect(self, line):
        parser = OptionParser()
        parser.prog = "connect"
        parser.add_option("-i", "--server-ip", dest="reg_ip",
                          default=service.get_my_ip(), metavar="SERVER_IP",
                          help="connect to SERVER_IP default to the ip of local host.")
        parser.add_option("-n", "--server-name", dest="reg_name", metavar="SERVER_NAME",
                      default="dexen_server", help="Connect to Server by logical name")        
        (options, args) = parser.parse_args(line.split())
        self.server.connect(options.reg_ip, options.reg_name)

    def do_archive_server_addr(self, line):
        parser = OptionParser()
        parser.prog = "archive_server_addr"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 0:
            parser.error(ARG_ERR_MSG)
        ip, port = self.server.get_address()
        msg = ""
        msg += "{0:22}{1}\n".format("archive server ip:",ip)
        msg += "{0:22}{1}\n".format("archive server port:", port)
        print msg

    def _get_job(self, job_id):
        if self.jobs.has_key(job_id):
            return self.jobs[job_id]
        job = self.server.get_job(job_id)
        assert job != None
        self.jobs[job_id] = job
        return job    
            
    def do_start_job(self, line):
        self.server.check_connection()
        parser = OptionParser()
        parser.prog = "start_job"
        parser.set_usage("start_job JOB_NAME [-d JOB_DEF_DIR]")
        parser.add_option("-d", "--job-dir", dest="job_dir",
                          default=os.getenv("JOB_DEF_DIR"), metavar="JOB_DEF_DIR", 
                          help="root directory that contains all the source code of the job definition.")
        options, args = parser.parse_args(line.split())
        
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job_name = args[0]
        if not job_name.isalnum():
            parser.error("Job name should only contain alphanumeric characters.")
        if not options.job_dir:
            parser.error("The path of the JOB_DEF has to be provided.")
        if not os.path.exists(options.job_dir):
            parser.error("The specified path for the JOB_DEF does not exist.")
        self.server.start_job(job_name, options.job_dir)
    
    def do_stop_job(self, line):
        self.server.check_connection()
        parser = OptionParser("stop_job JOB_ID")
        parser.prog = "stop_job"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        job.stop()
        
    def do_del_job(self, line):
        self.server.check_connection()
        parser = OptionParser("del_job JOB_ID")
        parser.prog = "del_job"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job_id = int(args[0])
        job = self._get_job(job_id)
        job.delete()
        del self.jobs[job_id]
    
    def do_list_jobs(self, line):
        self.server.check_connection()
        parser = OptionParser()
        parser.prog = "list_jobs"
        jobs = self.server.get_job_states()
        if len(jobs) > 0:
            job_state = jobs[0]
            print job_state.get_header()
        for job_state in jobs:
            assert isinstance(job_state, JobState)
            print job_state
            
    def do_download_job_data(self, line):
        self.server.check_connection()
        parser = OptionParser("download_job_data JOB_ID")
        parser.prog = "synch_job_data"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        print "Downloading data from Server...."
        num_acts = job.update()
        print num_acts, "data items have been downloaded."
    
    def do_get_job_log_file(self, line):
        parser = OptionParser("get_job_log_file JOB_ID FILE_PATH")
        parser.prog = "get_job_log_file"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        f = open(args[1], "w")
        f.write(job.get_action_log())
        f.close()
    
    def do_get_job_pareto_file(self, line):
        parser = OptionParser("get_job_pareto_file JOB_ID FILE_PATH [-m MAX_LEVEL]")
        parser.prog = "get_job_pareto_file"
        parser.add_option("-m", "--max-level", metavar="max_level", dest="max_level",
                          default=1, type="int", 
                          help="rank until this level. Default max_level is 1.")
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        f = open(args[1], "w")
        f.write(job.get_pareto_log(options.max_level))
        f.close()
        
    def do_add_job_score(self, line):
        parser = OptionParser("add_job_score JOB_ID NAME [MIN|MAX]")
        parser.prog = "add_job_score"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 3:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        job.add_score(*args[1:])
    
    def do_del_job_score(self, line):
        parser = OptionParser("del_job_score JOB_ID NAME")
        parser.prog = "del_job_score"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))     
        job.del_score(args[1])
    
    def do_list_job_scores(self, line):
        parser = OptionParser("list_job_scores JOB_ID")
        parser.prog = "list_job_scores"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        scores = job.get_score_metas()
        if len(scores) > 0:
            print ""
            score_meta = scores[0]
            print score_meta.get_header()
        for score_meta in scores:
            assert isinstance(score_meta, ScoreMeta)
            print score_meta
        print ""
        
    def do_exit(self, line):
        sys.exit(MY_EXIT)

    def do_help(self, line):
        msg = "{title:-^80}\n\n".format(title=" DEXEN CLI COMMAND LIST ")
        msg += "{0:45}Connect to a server.\n".format("connect SERVER_IP")
        msg += "{0:45}Get ip address and port number of the server\n".format("archive_server_addr")
        msg += "{0:45}that holds the archived data.\n".format("")
        msg += "{0:45}Start a new job.\n".format("start_job JOB_NAME SOURCE_PATH")
        msg += "{0:45}Stop a job.\n".format("stop_job JOB_ID")
        msg += "{0:45}Delete a job.\n".format("del_job JOB_ID")
        msg += "{0:45}List all the jobs.\n".format("list_jobs")
        msg += "{0:45}Download all the runtime data of a job.\n".format("download_job_data JOB_ID")
        msg += "{0:45}Create log file for a job based on the downloaded data.\n".format("get_job_log_file JOB_ID FILE_PATH")
        msg += "{0:45}Create a file for pareto information based on the added scores.\n".format("get_job_pareto_file JOB_ID FILE_PATH")
        msg += "{0:45}and downloaded data.\n".format("")
        msg += "{0:45}Add a name of the score attribute used in the job definition.\n".format("add_job_score JOB_ID NAME [MIN|MAX]")
        msg += "{0:45}Delete the name of the score.\n".format("del_job_score JOB_ID NAME")
        msg += "{0:45}List all the added scores.\n".format("list_job_scores JOB_ID")
        msg += "{0:45}Exit CLI.\n".format("exit")
        print msg


  
#===============================================================================
# Main
#===============================================================================
def start():
    try:
        c.cmdloop("")
    except SystemExit as e:
        if e.code == MY_EXIT:
            print "Exiting.\n"
        else:
            start()
    except KeyboardInterrupt:
        print "Keyboard interrupted.\n"
    except socket.error:
        print "Server is not reachable.\n"
        start()        
    except EOFError:
        print "EOFError: ServerDisconnected.\n"
        start()
    except Exception as e:
        print e
        if isinstance(e, AllNodeBusyException):
            print "Please start a new node or try again later.\n"
        start()
    except:
        print "Unknown Exception\n"
        start()

def main():
    global c
    msg = "{0:*^60}".format(" Welcom to DEXEN CLI ")
    print msg 
    c = MyCmd()
    start()  

if __name__ == "__main__":
    main()

